/*
  Ejemplo de programa servidor. Atiende peticiones aritmeticas, el protocolo de transporte
  sera el comm::Transport y el contenido del mensaje sera el resutaldo de un comm::Codec con los
  (x, y, op) -> El resultado sera estos tres componente mas result.

  Para poder probar el sistema de congestion se puede indicar un numero de milisegundos de 
  retardo aplicados a cada contestacion.

  Los clientes pueden ser: client.p o kclient.p
*/
#include <iostream>
#include <signal.h>

#include <nemesis.h>
#include <nemesis.comm.h>

#include <nemesis.xml.Node.h>
#include <nemesis.xml.Attribute.h>

#include <test.Request.h>
#include <test.Response.h>
#include <test.Communicator.h>

using namespace std;
using namespace test;

struct Context {
   Request request;
   Response response;
};

class MyCommunicator : public test::Communicator {
public:
   MyCommunicator () : 
      a_contexts ("Contexts")
   {;}
      
private:
   ThreadData <Context> a_contexts;

   void eventReceiveMessage (comm::ClientSocket&, const Message&) throw (RuntimeException);
};

class ArithmeticServer : public comm::Application {
public:
   ArithmeticServer ();
      
private:
   MyCommunicator a_communicator;
   comm::ServerSocket* a_serverSocket;

   void initialize () throw (RuntimeException);
   void run () throw (RuntimeException);
   xml::Node* asXML (xml::Node* app) const throw ();
};

using namespace std;
using namespace nemesis::comm;

int main (int argc, const char** argv)
{
   CommandLine& commandLine (CommandLine::instanciate ());
   ArithmeticServer app;

   srand (time (NULL));

   sigignore (SIGABRT);
   
   try {
      commandLine.initialize (argv, argc);
      commandLine.verify ();

      Logger::setLevel (Logger::Debug); 
      string traceFile ("server.");
      traceFile += nemesis::functions::asString ((int) getpid ());
      traceFile += ".trace";      
      Logger::initialize ("arithmeticServer", new TraceWriter (traceFile.c_str (),4096000));
 
      app.start ();
   }
   catch (Exception& ex) {
      cout << ex.asString () << endl;
   }
   
   return 0;
}

ArithmeticServer::ArithmeticServer () : 
   Application ("arithmeticServer", "Servidor de operaciones aritm�icas", "1.0") 
{
   CommandLine& commandLine (CommandLine::instanciate ());
      
   commandLine.add ("p", CommandLine::Argument::Mandatory, "Puerto en el que atender peticiones");
   commandLine.add ("a", CommandLine::Argument::Mandatory, "Direccin IP en la que atender");
   commandLine.add ("d", CommandLine::Argument::Mandatory, "Retardo aplicado a la contestacion");
   commandLine.add ("limit", CommandLine::Argument::Mandatory, "% de ocupacion que permitimos");
   commandLine.add ("r", CommandLine::Argument::Optional, "Indicador de reuso de direccin", false);
   commandLine.add ("n", CommandLine::Argument::Optional, "Numero de mensajes a servir", true);
   commandLine.add ("trace", CommandLine::Argument::Optional, "Nivel de trazas (debug,warning, error,...)");
}

//-----------------------------------------------------------------------------------------
// Inicializa el servidor de sockets.
//-----------------------------------------------------------------------------------------
void ArithmeticServer::initialize () 
   throw (RuntimeException)
{
   CommandLine& cl (CommandLine::instanciate ());

   int port = cl.getIntegerValue ("p");
   const comm::Device* device = Network::instanciate ().find (Device::asAddress (cl.getValue ("a")));

   a_serverSocket = new ServerSocket (INetAddress (device, port), cl.exists ("r"));
   a_serverSocket->setCategory (777);
}

//-----------------------------------------------------------------------------------------
// Atiende las peticiones.
// Cuando hay un nuevo mensaje invocar�a Communicator::eventReceiveMessage
//-----------------------------------------------------------------------------------------
void ArithmeticServer::run ()
   throw (RuntimeException)
{
   CommandLine& cl (CommandLine::instanciate ());

   a_communicator.attach (a_serverSocket);
   a_communicator.setDelay (cl.getIntegerValue ("d"));

   if (cl.exists ("n") == true)
      a_communicator.setMaxMessage (cl.getIntegerValue ("n"));

   if (cl.exists ("trace"))
      Logger::setLevel (Logger::asLevel (cl.getValue ("trace")));

   CongestionController::instanciate ().setLimit (cl.getIntegerValue ("limit"));

   a_communicator.accept ();
}

xml::Node* ArithmeticServer::asXML (xml::Node* app) const 
   throw ()
{
   xml::Node* node = app::Application::asXML (app);
   
   node->createAttribute ("MaxMessage", a_communicator.getMaxMessage ());
   node->createAttribute ("Message", a_communicator.getMessage ());
   
   return node;
}

//-----------------------------------------------------------------------------------------
// Manejador de peticiones.
// Calcular�la operacin solicitada y devolver�el resultado.
//
// clientSocket: Socket cliente por el que podemos responder a la peticin.
// transport: Instancia del transporto que ha interpretado el mensaje (getMessage).
//-----------------------------------------------------------------------------------------
void MyCommunicator::eventReceiveMessage (ClientSocket& clientSocket, const Message& message)
   throw (RuntimeException)
{
   LOGMETHOD (TraceMethod tm ("MyCommunicator", "eventReceiveMessage", FILE_LOCATION));

   if (canContinue (clientSocket) == false)
      return;   

   delay ();   
    
   Context& context = a_contexts.get ();
   Request& request (context.request);
   Response& response (context.response);

   request.decode (message.getBody ());

   response.x = request.x;
   response.y = request.y;
   response.initTime = request.initTime;

   switch (response.op = request.op) {
      case '+':
         response.result = request.x + request.y;
         break;
      case '-':
         response.result = request.x - request.y;
         break;
      case '*':
         response.result = request.x * request.y;
         break;
      case '/':
         response.result = (request.y != 0) ? (request.x / request.y): 0;
         break;
   }

   LOGINFORMATION (
      string msg = nemesis::functions::asString ("%d %c %d = %d", request.x, request.op, request.y, response.result);
      msg += nemesis::functions::asText (" | InitTime: ",  response.initTime);
      Logger::information (msg, FILE_LOCATION);
   )

   try {
      clientSocket.send (response);
   }
   catch (Exception& ex) {
      ex.trace ();
   }
}

